#include "virtio-internal.h"

#include <virtio/virtio.h>

#include <types.h>
#include <riscv.h>
#include <memlayout.h>
#include <io.h>
#include <log.h>
#include <string.h>
#include <malloc.h>
#include <core/class.h>
#include <block/block.h>
#include <interrupt/interrupt.h>
#include <core/semaphore.h>
#include <core/waitqueue.h>
#include <core/input-event-codes.h>
#include <core/event.h>

#define LOGI(...)

#define NUM_TABLET_KEY_SUPPORTED 3
#define NUM_TABLET_ABS_SUPPORTED 2
static uint tablet_key_codes[] = {BTN_LEFT, BTN_RIGHT, BTN_MIDDLE};
static uint tablet_abs_codes[] = {ABS_X, ABS_Y};

class(virtio_tablet_t, device_t)
{
    struct virtio_mmio_desc_t *desc;
    struct virtio_queue_t eventq;
    struct virtio_queue_t statusq;
    struct wait_queue_head wq_head;
    struct virtio_input_absinfo info[NUM_TABLET_ABS_SUPPORTED];
    uint32 width;
    uint32 height;
};

class_impl(virtio_tablet_t, device_t){};

void virtio_tablet_check_if_support(virtio_tablet_t *vtablet)
{
    volatile struct virtio_input_config *config = (struct virtio_input_config *)virtio_mmio_get_config(vtablet->desc);
    virtio_input_check_if_support(config, VIRTIO_INPUT_CFG_EV_BITS, EV_KEY, tablet_key_codes, NUM_TABLET_KEY_SUPPORTED);

    for (int i = 0; i < NUM_TABLET_ABS_SUPPORTED; i++)
    {
        virtio_input_config_select(config, VIRTIO_INPUT_CFG_ABS_INFO, tablet_abs_codes[i]);
        vtablet->info[tablet_abs_codes[i]] = config->u.abs;
        LOGD("min: ", $(config->u.abs.min));
        LOGD("max: ", $(config->u.abs.max));
        LOGD("fuzz: ", $(config->u.abs.fuzz));
        LOGD("flat: ", $(config->u.abs.flat));
        LOGD("res: ", $(config->u.abs.res));
    }
}

static uint32 virtio_block_get_features(uint32 features)
{
    return 0;
}

void virtio_tablet_handle_key_event(uint16 code, uint32 value)
{
    push_event(&(struct input_event_t){
        .type = EV_KEY,
        .code = code,
        .value = value,
    });
}

void virtio_tablet_handle_abs_event(virtio_tablet_t *vtablet, uint16 code, uint32 value)
{
    struct input_event_t e = {
        .type = EV_ABS,
        .code = code,
    };

    switch (code)
    {
    case ABS_X:
    {
        uint32 range = vtablet->info[ABS_X].max - vtablet->info[ABS_X].min;
        uint32 x = (vtablet->width * (value - vtablet->info[ABS_X].min) + range / 2) / range;
        e.value = x;
        break;
    }
    case ABS_Y:
    {
        uint32 range = vtablet->info[ABS_Y].max - vtablet->info[ABS_Y].min;
        uint32 y = (vtablet->height * (value - vtablet->info[ABS_Y].min) + range / 2) / range;
        e.value = y;
        break;
    }
    default:
        LOGI("code " $(code) " not recognized");
        return;
    }
    push_event(&e);
}

void virtio_tablet_handle_event(virtio_tablet_t *vtablet, struct virtio_input_event *b)
{
    switch (b->type)
    {
    case EV_SYN:
        break;
    case EV_KEY:
        virtio_tablet_handle_key_event(b->code, b->value);
        break;
    case EV_ABS:
        virtio_tablet_handle_abs_event(vtablet, b->code, b->value);
        break;
    default:
        LOGE("EV TYPE " $(b->type) " NOT RECOGNIZED");
    }
}

static void free_desc(struct vring_desc *desc, void *data)
{
    virtio_tablet_t *vtablet = (virtio_tablet_t *)data;
    if (desc->flags == VRING_DESC_F_WRITE)
    {
        struct virtio_input_event *event = (struct virtio_input_event *)desc->addr;
        virtio_tablet_handle_event(vtablet, event);
        free(event);
    }
    else
    {
        PANIC("");
    }
}

static void irq_handler(void *data)
{
    LOGI();
    virtio_tablet_t *vtablet = (virtio_tablet_t *)data;
    struct virtio_queue_t *eventq = &vtablet->eventq;
    virtio_mmio_interrupt_ack(vtablet->desc);
    virtio_device_irq_handler(eventq, free_desc, vtablet);
    virtio_input_request_event(vtablet->desc, eventq);
}

int virtio_tablet_probe(device_t *this, xjil_value_t *value)
{
    virtio_tablet_t *vtablet = dynamic_cast(virtio_tablet_t)(this);
    uint32 device_id = xjil_read_int(value, "device_id", -1);
    int virtio_mmio_bus = xjil_read_int(value, "virtio-mmio-bus", -1);
    int irq = xjil_read_int(value, "interrupt", -1);

    if (device_id == -1)
    {
        LOGE("device_id == -1");
        return -1;
    }

    vtablet->width = xjil_read_u32(value, "width", 0);
    vtablet->height = xjil_read_u32(value, "height", 0);

    struct virtio_mmio_desc_t *desc = virtio_mmio_search_device(device_id, virtio_mmio_bus);

    if (!desc)
    {
        return -1;
    }

    vtablet->desc = desc;
    virtio_mmio_setup(desc, virtio_block_get_features);
    virtio_mmio_queue_set(desc, &vtablet->eventq, 0);
    virtio_mmio_queue_set(desc, &vtablet->statusq, 1);

    struct virtio_queue_t *data = &vtablet->eventq;

    virtio_tablet_check_if_support(vtablet);
    virtio_input_request_event(vtablet->desc, data);

    init_waitqueue_head(&vtablet->wq_head);
    request_irq(irq, irq_handler, vtablet);
    return 0;
}

constructor(virtio_tablet_t)
{
    dynamic_cast(device_t)(this)->name = "virtio-tablet";
    dynamic_cast(device_t)(this)->probe = virtio_tablet_probe;
}